package com.jwetherell.my.problems.dynamicprogramming;

/**
 * http://www.geeksforgeeks.org/dynamic-programming-set-4-longest-common-
 * subsequence/
 * 
 * LCS Problem Statement: Given two sequences, find the length of longest
 * subsequence present in both of them. A subsequence is a sequence that appears
 * in the same relative order, but not necessarily contiguous. For example,
 * “abc”, “abg”, “bdf”, “aeg”, ‘”acefg”, .. etc are subsequences of “abcdefg”.
 * So a string of length n has 2^n different possible subsequences.
 * 
 * It is a classic computer science problem, the basis of diff (a file
 * comparison program that outputs the differences between two files), and has
 * applications in bioinformatics.
 * 
 * Examples:
 * 
 * LCS for input Sequences “ABCDGH” and “AEDFHR” is “ADH” of length 3. LCS for
 * input Sequences “AGGTAB” and “GXTXAYB” is “GTAB” of length 4.
 * 
 * @author yatendra
 * 
 */
public class LCS_LongestCommonSubsequence {

	private static final int NULL_VALUE = -1;

	private char[] x;
	private char[] y;
	private int m; // length of x
	private int n; // length of y

	private int[][] c;

	public static void main(String[] args) {
		LCS_LongestCommonSubsequence lcsProblem = new LCS_LongestCommonSubsequence();

//		String xStr = "ABCDGH";
//		String yStr = "AEDFHR";
		
//		String xStr = "AGGTAB";
//		String yStr = "GXTXAYB";
		
		String xStr = "BDCABA";
		String yStr = "ABCBDAB";
		
		lcsProblem.x = xStr.toCharArray();
		lcsProblem.y = yStr.toCharArray();

		lcsProblem.printInputs();

		String lcs = lcsProblem.findLCS();
		System.out.println("LCS: " + lcs);

	}

	private String findLCS() {

		m = x.length;
		n = y.length;

		/*
		 * c represents a matrix
		 * 
		 * c[i][j] represents the length of LCS in seq1[0...i] and seq2[0...j]
		 * 
		 * all cell values will automatically be set to 0 so no need to
		 * explicitly set each cell to 0
		 */
		c = new int[m + 1][n + 1];
		for (int i = 0; i < m + 1; i++)
			for (int j = 0; j < n + 1; j++)
				c[i][j] = NULL_VALUE;

		/*
		 * Finding LCS length in 3 different implementations
		 */
		long startTime = System.nanoTime();
		int lcsLength = computeLCSLength_Iteratively();
		long totalTimeTaken = System.nanoTime() - startTime;
		System.out.println("LCS length (iterative): " + lcsLength + " [Time(nanoseconds): " + totalTimeTaken + "]");

		startTime = System.nanoTime();
		lcsLength = computeLCSLength_Recursively(m, n);
		totalTimeTaken = System.nanoTime() - startTime;
		System.out.println("LCS length: (recursive): " + lcsLength + " [Time(nanoseconds): " + totalTimeTaken + "]");

		startTime = System.nanoTime();
		lcsLength = computeLCSLength_Recursively_WithMemoization(m, n);
		totalTimeTaken = System.nanoTime() - startTime;
		System.out.println("LCS length: (memoized)" + lcsLength + " [Time(nanoseconds): " + totalTimeTaken + "]");

		if (lcsLength <= 0)
			return null;

		for (int i = 0; i < m + 1; i++) {
			for (int j = 0; j < n + 1; j++)
				System.out.print(c[i][j] + " ");
			System.out.println();
		}

		return findLCS_String();
	}

	/**
	 * Time: O(m*n) Space: O(m*n) for matrix
	 */
	private int computeLCSLength_Iteratively() {

		// fill first row - base-case
		for (int col = 0; col < n + 1; col++)
			c[0][col] = 0;

		// fill first col - base case
		for (int row = 0; row < m + 1; row++)
			c[row][0] = 0;

		for (int row = 1; row < m + 1; row++) { // calculating the lengths for
												// rest-of-the-cases
			for (int col = 1; col < n + 1; col++) {
				if (x[row - 1] == y[col - 1]) // row-1 instead of row because of
												// dummy row in the matrix
					c[row][col] = c[row - 1][col - 1] + 1;
				else
					c[row][col] = Math.max(c[row][col - 1], c[row - 1][col]);
			}
		}

		return c[m][n]; // return the desired result from all the computed
						// results
	}

	/**
	 * Worst-case will occur for this recursive algo when (x[i] != y[j]) for all
	 * i and j i.e length of LCS is 0. This will be the worst case because
	 * 
	 * 1. Then it will enter in the 'else' block where we are calling the
	 * recursive function twice while in the 'if' block we are calling the
	 * recursive function only once. Because of calling the recursive function
	 * twice, it will generate a "BINARY" recursion tree and the height of that
	 * tree will be (m+n). So the number of nodes in that tree will be 2^(m+n)
	 * i.e exponential. And we know that exponential is very SLOW. Even our
	 * brute-force solution running time was (m * 2^n) i.e exponential. So we
	 * didn't gain any advantage by adopting this algo. But if you observe the
	 * recursion tree, you will notice that we solve several same sub-problems
	 * multiple times. So we can implement a memoized-version of the below
	 * recursive function to reduce the running time from exponential to m*n.
	 * Why m*n? Because out of 2^(m+n) sub-problems in the recursion tree, there
	 * are only m*n distinct sub-problems
	 * 
	 * 2. In the 'else' block, we are decreasing only one index at a time (i-1,
	 * j) or (i, j-1) while in the 'if' block, we are decreasing both the
	 * indices
	 * 
	 */
	private int computeLCSLength_Recursively(int i, int j) {

		// base-case: if the row or the column is dummy
		if (i == 0 || j == 0)
			return 0;

		if (x[i - 1] == y[j - 1]) // i-1 and not i because of dummy row/col in
									// the matrix
			c[i][j] = computeLCSLength_Recursively(i - 1, j - 1) + 1;
		else
			c[i][j] = Math.max(computeLCSLength_Recursively(i, j - 1), computeLCSLength_Recursively(i - 1, j));

		return c[i][j];
	}

	/**
	 * Time: O(m*n) Space: O(m*n) for storing c matrix data whose dimension is
	 * m*n
	 */
	private int computeLCSLength_Recursively_WithMemoization(int i, int j) {
		if (c[i][j] == NULL_VALUE) {

			// base-case: if the row or the column is dummy
			if (i == 0 || j == 0)
				c[i][j] = 0;

			if (x[i - 1] == y[j - 1]) // i-1 and not i because of dummy row/col
										// in the matrix
				c[i][j] = computeLCSLength_Recursively(i - 1, j - 1) + 1;
			else
				c[i][j] = Math.max(computeLCSLength_Recursively(i, j - 1), computeLCSLength_Recursively(i - 1, j));
		}
		return c[i][j];
	}

	/**
	 * Find LCS string by backtracking the two sequences and the matrix
	 */
	private String findLCS_String() {
		
		printAllLCS_Strings_Recursively(x.length, y.length, "");
		
		return findLCS_String_Recursively(x.length, y.length);
	}

	private String findLCS_String_Recursively(int i, int j) {

		if (i == 0 || j == 0) // base-case
			return "";

		/*
		 * Reducing both i & j by 1 because they represent matrix index and
		 * matrix contains one dummy row and column.
		 */
		if (x[i - 1] == y[j - 1]) {
			return findLCS_String_Recursively(i - 1, j - 1) + x[i - 1];
		} else {
			if (c[i - 1][j] >= c[i][j - 1])
				return findLCS_String_Recursively(i - 1, j);
			else
				return findLCS_String_Recursively(i, j - 1);
		}

	}
	
	private void printAllLCS_Strings_Recursively(int i, int j, String currentLCSStr) {;

		if (i == 0 || j == 0) { // base-case
			System.out.println("LCS_String: " + currentLCSStr);
			return;
		}
		
		/*
		 * Reducing both i & j by 1 because they represent matrix index and
		 * matrix contains one dummy row and column.
		 */
		if (x[i-1] == y[j-1]) {
			printAllLCS_Strings_Recursively(i-1, j-1, x[i-1] + currentLCSStr);
		} else {
			if (c[i-1][j] > c[i][j-1]) // top-cell
				printAllLCS_Strings_Recursively(i-1, j, currentLCSStr);
			else if (c[i-1][j] < c[i][j-1]) // left-cell
				printAllLCS_Strings_Recursively(i, j-1, currentLCSStr);
			else {
				
				printAllLCS_Strings_Recursively(i, j-1, currentLCSStr);
				printAllLCS_Strings_Recursively(i-1, j, currentLCSStr);
				
			}
		}
	}

	private void printInputs() {
		System.out.print("Seq1: ");
		for (int i = 0; i < x.length; i++) {
			System.out.print(x[i]);
		}
		System.out.println();

		System.out.print("Seq2: ");
		for (int i = 0; i < y.length; i++) {
			System.out.print(y[i]);
		}
		System.out.println();
	}

}